#pragma once

#include "nef_common.h"
#include "type_factory.h"
#include <vector>
#include <memory>

#define IxTypePtr std::shared_ptr<IxType>
#define IxTypeMany std::vector<IxTypePtr>
#define IxTypeManyPtr std::shared_ptr<IxTypeMany>

#define OWNER_SETTER void (Owner::*F)(const IxTypePtr &)
#define OWNER_MANY_SETTER void (Owner::*F)(const IxTypeManyPtr &)

#define OWNER_MEMBER IxTypePtr Owner::*MV
#define OWNER_MANY_MEMBER IxTypeMany Owner::*MV

#define OWNER_FACTORY_SETTER void (Owner::*F)(const typename type_factory<IxType>::Ptr &)
#define OWNER_FACTORY_MEMBER typename type_factory<IxType>::Ptr Owner::*MV

namespace nef {

class Catalog;

/* Marker interface that defines an import of the specified type, and sets it through member variable resolution.
   The member variable is std::shared_ptr<IxType>
*/
template<typename Owner, typename IxType, OWNER_MEMBER, const char *ContractName = NO_CONTRACT, bool Required = true>
struct nef_import { };

/* Marker interface that defines an import of the specified type, and sets it through a member function
   This function is called with the following signature: void (const std::shared_ptr<IxType> &)
 */
template<typename Owner, typename IxType, OWNER_SETTER, const char *ContractName = NO_CONTRACT, bool Required = true>
struct nef_import_fn {};

/* Marker interface that defines an import of many IxTypes. 
   The member variable is std::vector<std::shared_ptr<IxType>>
*/
template<typename Owner, typename IxType, OWNER_MANY_MEMBER, const char *ContractName = NO_CONTRACT>
struct nef_import_many {};

/* Marker interface that declares that there might be multiple IxType's with the same ContractName, and allows you to get all of them.
   This function is called with the following signature: void (const std::shared_ptr<std::vector<std::shared_ptr<IxType>>> &)
*/
template<typename Owner, typename IxType, OWNER_MANY_SETTER, const char *ContractName = NO_CONTRACT>
struct nef_import_many_fn {};


template<typename Owner, typename IxType, OWNER_FACTORY_MEMBER, const char *ContractName = NO_CONTRACT, bool Required = true>
struct nef_import_factory {};

template<typename Owner, typename IxType, OWNER_FACTORY_SETTER, const char *ContractName = NO_CONTRACT, bool Required = true>
struct nef_import_factory_fn {};


template<typename T>
struct nef_import_resolver
{
	// Any import definition that is not of type nef_import is an error
	static_assert(sizeof(T) == 0, "The specified import definition must be of type nef_import or nef_import_many");

	static void Resolve(Catalog &cat, T &val)
	{

	}
};

#pragma region Basic Import
template<typename Owner, typename IxType, OWNER_MEMBER, const char *ContractName, bool Required>
struct nef_import_resolver<nef_import<Owner, IxType, MV, ContractName, Required>> 
{
	static void Resolve(Catalog &cat, Owner &owner)
	{
		static const std::string CONTRACT_NAME = ContractName;

		auto spVal = cat.GetExportedValue<IxType>(CONTRACT_NAME, Required);

		// Set the value
		(owner.*MV) = spVal;
	}
};

template<typename Owner, typename IxType, OWNER_SETTER, const char *ContractName, bool Required>
struct nef_import_resolver<nef_import_fn<Owner, IxType, F, ContractName, Required>> 
{
	static void Resolve(Catalog &cat, Owner &owner)
	{
		static const std::string CONTRACT_NAME = ContractName;

		auto spVal = cat.GetExportedValue<IxType>(CONTRACT_NAME, Required);

		// Set the value
		(owner.*F)(spVal);
	}
};
#pragma endregion

#pragma region Many Import
template<typename Owner, typename IxType, OWNER_MANY_MEMBER, const char *ContractName>
struct nef_import_resolver<nef_import_many<Owner, IxType, MV, ContractName>>
{
	static void Resolve(Catalog &cat, Owner &owner)
	{
		static const std::string CONTRACT_NAME = ContractName;

		std::vector<IExport::Ptr> exports;
		cat.FindAllExports<IxType>(CONTRACT_NAME, exports);

		IxTypeMany &vals = (owner.*MV);
		vals.clear();

		for (const IExport::Ptr &exp : exports)
			vals.push_back(exp->GetValue<IxType>(cat));
	}
};

template<typename Owner, typename IxType, OWNER_MANY_SETTER, const char *ContractName>
struct nef_import_resolver<nef_import_many_fn<Owner, IxType, F, ContractName>>
{
	static void Resolve(Catalog &cat, Owner &owner)
	{
		static const std::string CONTRACT_NAME = ContractName;

		std::vector<IExport::Ptr> exports;
		cat.FindAllExports<IxType>(CONTRACT_NAME, exports);

		auto vals = std::make_shared<IxTypeMany>();

		for (const IExport::Ptr &exp : exports)
			vals->push_back(exp->GetValue<IxType>(cat));

		// Set the value
		(owner.*F)(vals);
	}
};
#pragma endregion

#pragma region Factory Import
template<typename Owner, typename IxType, OWNER_FACTORY_MEMBER, const char *ContractName, bool Required>
struct nef_import_resolver<nef_import_factory<Owner, IxType, MV, ContractName, Required>>
{
	static void Resolve(Catalog &cat, Owner &owner)
	{
		static const std::string CONTRACT_NAME = ContractName;

		auto xp = cat.GetExportFactory<IxType>(CONTRACT_NAME, Required);

		// Set the value
		(owner.*MV) = xp;
	}
};

template<typename Owner, typename IxType, OWNER_FACTORY_SETTER, const char *ContractName, bool Required>
struct nef_import_resolver<nef_import_factory_fn<Owner, IxType, F, ContractName, Required>>
{
	static void Resolve(Catalog &cat, Owner &owner)
	{
		static const std::string CONTRACT_NAME = ContractName;

		auto xp = cat.GetExportFactory<IxType>(CONTRACT_NAME, Required);

		// Set the value
		(owner.*F)(xp);
	}
};
#pragma endregion

template<typename T, typename ImportType>
void resolve_import(Catalog &cat, T &val)
{
	nef_import_resolver<ImportType>::Resolve(cat, val);
}

}